#include "raylib.h"

#define ROWS 24
#define COLS 10

typedef struct PosRowCol { int row; int col; } PosRowCol;

// 形状
typedef struct Shape {
    int idx;       // 在图片中的索引，用来绘制不同的颜色
    int sz;        // 尺寸-在排列中的正方形尺寸
    int srt[4][4]; // 排列
} Shape;

const int CELL_WIDTH = 24;
const int CELL_HEIGHT = 24;
const int LINE_WIDTH = 2;
const float MOVE_INTERVAL = 0.5;
const Shape shapes[7] = {
    { 0, 2, {{1, 1, 0, 0}, {1, 1, 0, 0}}},
    { 2, 3, {{0, 0, 0}, {1, 1, 0}, {0, 1, 1}}},
    { 2, 3, {{0, 0, 0}, {0, 1, 1}, {1, 1, 0}}},
    { 3, 3, {{0, 1, 0}, {0, 1, 0}, {0, 1, 1}}},
    { 4, 3, {{0, 0, 0}, {1, 1, 1}, {0, 1, 0}}},
    { 5, 3, {{0, 1, 0}, {0, 1, 0}, {1, 1, 0}}},
    { 1, 4, {{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}}}
};

bool isOver = false;
int grid[ROWS][COLS] = {-1};
Texture texture;
Texture textRestart;
Shape curShape;
int curShapeRow;
int curShapeCol;
float curShapeMoveInterval = 0;
float speedFactor = 1;

void init();
void initGame();

void drawBg();
void drawBlock();
void driveCurShape(float dt);
void drawCurShape();
void drawFail();

Vector2 getPosOfRowCol(int row, int col);
PosRowCol getRowColOfPos(Vector2 pos);
bool showButton(Texture text, Vector2 pos);
void newShape();
void eliminate();
void checkFail();
void checkKeyPress();
bool checkDown();
bool checkLeft();
bool checkRight();
void rotate();

int main() {
    init();
    while (!WindowShouldClose()) {
        checkKeyPress();
        BeginDrawing();
            drawBg();
            driveCurShape(GetFrameTime());
            drawCurShape();
            drawBlock();
            drawFail();
        EndDrawing();
    }

    CloseWindow();
    return 0;
}

void init() {
    InitWindow(242, 482, "Tetris");
    SetTargetFPS(60);
    texture = LoadTexture("res/texture.png");
    textRestart = LoadTexture("res/restart.png");
    initGame();
}

void initGame() {
    isOver = false;
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++)
            grid[row][col] = -1;
    }

    newShape();
}

void drawBg() {
    ClearBackground((Color){188, 159, 218, 255});
    for (int row = 0; row <= ROWS; row++)
        DrawRectangle(0, row * CELL_HEIGHT, GetScreenWidth(), LINE_WIDTH, WHITE);
    for (int col = 0; col <= COLS; col++)
        DrawRectangle(col * CELL_WIDTH, 0, LINE_WIDTH, GetScreenHeight(), WHITE);
}

void drawBlock() {
    for (int row = 4; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            if (grid[row][col] != -1) {
                Vector2 pos = getPosOfRowCol(row, col);
                pos.x += LINE_WIDTH;
                pos.y += LINE_WIDTH;
                DrawTextureRec(
                    texture, 
                    (Rectangle){
                        0, 
                        grid[row][col] * CELL_HEIGHT, 
                        CELL_WIDTH,
                        CELL_HEIGHT
                    },
                    pos, 
                    WHITE
                );
            }
        }
    }
}

void driveCurShape(float dt) {
    if (isOver) return;
    curShapeMoveInterval += dt * speedFactor;
    if (curShapeMoveInterval > MOVE_INTERVAL) {
        curShapeMoveInterval -= MOVE_INTERVAL;
        if (checkDown()) {
            curShapeRow++;
        } else {
            // 将当前的形状放到grid中
            for (int i = 0; i < 4; i++) {
                for (int j = 0; j < 4; j++) {
                    if (!curShape.srt[i][j]) continue;
                    int r = curShapeRow + i;
                    int c = curShapeCol + j;
                    grid[r][c] = curShape.idx;
                }
            }
            eliminate();
            checkFail();
            newShape();
        }
    }
}

void drawCurShape() {
    for (int row = 0; row < 4; row++) {
        for (int col = 0; col < 4; col++) {
            if (!curShape.srt[row][col]) continue;
            int r = row + curShapeRow;
            int c = col + curShapeCol;
            Vector2 pos = getPosOfRowCol(r, c);
            DrawTextureRec(
                texture,
                (Rectangle) {
                    0,
                    curShape.idx * CELL_HEIGHT,
                    CELL_WIDTH,
                    CELL_HEIGHT,
                },
                (Vector2){pos.x + LINE_WIDTH, pos.y + LINE_WIDTH},
                WHITE
            );
        }
    }
}

void eliminate() {
    int row = ROWS - 1;
    while (row > 0) {
        bool isRowFull = true;
        for (int col = 0; col < COLS; col++) {
            if (grid[row][col] == -1) {
                isRowFull = false;
                break;
            }
        }

        if (isRowFull && row > 0) {
            for (int r = row; r > 0; r--) {
                for (int c = 0; c < COLS; c++) {
                    grid[r][c] = grid[r -1][c];
                }
            }
        } else {
            row--;
        }
    }
}

void checkFail() {
    for (int row = 0; row < 4; row++) {
        for (int col = 0; col < COLS; col++) {
            if (grid[row][col] > -1) {
                isOver = true;
                return;
            }
        }
    }
}

void newShape() {
    int idx = GetRandomValue(0, 6);
    curShape.idx = shapes[idx].idx;
    curShape.sz = shapes[idx].sz;
    for (int i = 0; i != 4; i ++) {
        for (int j = 0; j != 4; j++) {
            curShape.srt[i][j] = shapes[idx].srt[i][j];
        }
    }
    curShapeRow = 4 - shapes[idx].sz;
    curShapeCol = 3;
    curShapeMoveInterval = 0;
}

Vector2 getPosOfRowCol(int row, int col) {
    int x = col * CELL_WIDTH;
    int y = (row - 4) * CELL_HEIGHT;
    return (Vector2){x, y};
}

PosRowCol getRowColOfPos(Vector2 pos) {
    PosRowCol rcPos = {-1, -1};
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            Vector2 posOfCell = getPosOfRowCol(row, col);
            if (pos.x >= posOfCell.x
                && pos.x <= posOfCell.x + CELL_WIDTH
                && pos.y >= posOfCell.y
                && pos.y <= posOfCell.y + CELL_HEIGHT) {
                rcPos.row = row;
                rcPos.col = col;
                break;
            }
        }
    }
    return rcPos;
}

void drawFail() {
    if (!isOver) return;
    DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(),
        (Color){0, 0, 0, 128});
    int textWidth = MeasureText("YOU FAIL", 40);
    DrawText("YOU FAIL", (GetScreenWidth() - textWidth)/2,
        GetScreenHeight() / 3, 40, WHITE);
    if (showButton(textRestart, (Vector2){
        (GetScreenWidth() - textRestart.width)/2,
        GetScreenHeight() * 2 / 3})) {
        initGame();
    }
}

void checkKeyPress() {
    if (IsKeyPressed(KEY_R)) initGame();
    if (isOver) return;

    if (IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_A)) {
        if (checkLeft()) curShapeCol--;
    } else if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_D)) {
        if (checkRight()) curShapeCol++;
    } else if (IsKeyPressed(KEY_UP) || IsKeyPressed(KEY_W)) {
        rotate();
    }
    
    if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) {
        speedFactor = 15;
    } else {
        speedFactor = 1;
    }
}

bool checkDown() {
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            if (!curShape.srt[i][j]) continue;
            int r = curShapeRow + i + 1;
            if (r >= ROWS) return false;
            int c = curShapeCol + j;
            if (grid[r][c] != -1) return false;
        }
    }
    return true;
}

bool checkLeft() {
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            if (!curShape.srt[i][j]) continue;
            int r = curShapeRow + i;
            int c = curShapeCol + j - 1;
            if (c < 0) return false;
            if (grid[r][c] != -1) return false;
        }
    }
    return true;
}

bool checkRight() {
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            if (!curShape.srt[i][j]) continue;
            int r = curShapeRow + i;
            int c = curShapeCol + j + 1;
            if (c >= COLS) return false;
            if (grid[r][c] != -1) return false;
        }
    }
    return true;
}

void rotate() {
    int tmpSrt[4][4] = {0};
    for (int i = 0; i < curShape.sz; i++) {
        for (int j = 0; j < curShape.sz; j++) {
            tmpSrt[i][j] = curShape.srt[curShape.sz - j - 1][i];
        }
    }

    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            if (!tmpSrt[i][j]) continue;
            int r = curShapeRow + i;
            int c = curShapeCol + j;
            if (r < 0 || r >= ROWS || c < 0 || c >= COLS) {
                return;
            }
            if (grid[r][c] > -1) return;
        }
    }

    for (int i = 0; i < curShape.sz; i++) {
        for (int j = 0; j < curShape.sz; j++) {
            curShape.srt[i][j] = tmpSrt[i][j];
        }
    }
}

bool showButton(Texture text, Vector2 pos) {
    Color color = WHITE;
    Rectangle rec = (Rectangle){pos.x, pos.y, text.width, text.height};
    Vector2 mousePoint = GetMousePosition();
    bool clicked = false;
    if (CheckCollisionPointRec(mousePoint, rec)) {
        if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
            color = (Color){127, 127, 127, 255};
        } else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
            clicked = true;
        } else {
            color = (Color){200, 200, 200, 255};
        }
    }
    DrawTexture(text, pos.x, pos.y, color);

    return clicked;
}
